/**
 * 金牛座-表数据服务类
 * 提供通用的单表数据CRUD操作和导入导出功能
 * @author sun
 * @date 2024-03-27
 */

import { autoPost } from './index';

// 默认 params
export interface IDefaultParams {
  /** 接口名称 */
  action?: string;
  /** 接口编号 */
  no?: string;
  /** 其他参数 */
  [key: string]: any;
}
/**
 * 分页查询结果接口
 */
export interface IPageResult<T> {
  /** 总记录数 */
  total: number;
  /** 当前页数据 */
  rows: T[];
  /** 当前页码 */
  page?: number;
  /** 每页记录数 */
  pagesize?: number;
}

/**
 * 查询条件操作符类型
 * @description 定义查询条件的操作符，如等于、大于、小于、模糊匹配等
 */
export type ConditionOperator =
  | '='
  | '!='
  | '>'
  | '>='
  | '<'
  | '<='
  | 'like'
  | 'not-like'
  | 'in'
  | 'not-in'
  | 'between'
  | 'not-between';

/**
 * 查询条件逻辑操作符类型
 * @description 定义查询条件之间的逻辑关系，如与(and)、或(or)
 */
export type LogicalOperator = 'and' | 'or';

/**
 * 提取对象的键作为联合类型
 * @description 用于获取表模型的所有字段名
 */
export type KeysOf<T> = keyof T;

/**
 * 模糊查询操作符
 * @description 用于模糊查询的操作符
 */
export type LikeOperators = 'like' | 'not-like';

/**
 * 范围查询操作符
 * @description 用于范围查询的操作符
 */
export type RangeOperators = '>' | '>=' | '<' | '<=';

/**
 * 相等查询操作符
 * @description 用于相等查询的操作符
 */
export type EqualityOperators = '=' | '!=';

/**
 * 包含查询操作符
 * @description 用于包含查询的操作符
 */
export type ContainmentOperators = 'in' | 'not-in';

/**
 * 区间查询操作符
 * @description 用于区间查询的操作符
 */
export type BetweenOperators = 'between' | 'not-between';

/**
 * 为IN/NOT-IN条件定义的类型
 * @description 完整的IN条件定义，包括操作符、字段和值数组
 * @example ['in', 'status', [1,2,3]] - status在1、2、3之中
 */
export type InCondition<T> = [ContainmentOperators, keyof T, any[]];

/**
 * 为BETWEEN/NOT-BETWEEN条件定义的类型
 * @description 完整的BETWEEN条件定义，包括操作符、字段和起止值
 * @example ['between', 'age', [18, 30]] - age在18到30之间
 */
export type BetweenCondition<T> = [BetweenOperators, keyof T, [any, any]];

/**
 * 为比较操作符定义的类型
 * @description 完整的比较条件定义，包括操作符、字段和比较值
 * @example ['=', 'id', 1001] - id等于1001
 * @example ['>', 'age', 18] - age大于18
 */
export type CompareCondition<T> = [EqualityOperators | RangeOperators, keyof T, any];

/**
 * 为模糊查询定义的类型
 * @description 完整的模糊查询条件定义，包括操作符、字段和模糊匹配值
 * @example ['like', 'name', '%张%'] - name包含"张"
 */
export type LikeCondition<T> = [LikeOperators, keyof T, string];

/**
 * 所有基本条件类型的联合
 * @description 所有基本条件的联合类型
 */
export type BasicCondition<T> =
  | InCondition<T>
  | BetweenCondition<T>
  | CompareCondition<T>
  | LikeCondition<T>;

/**
 * 逻辑组合条件类型
 * @description 逻辑组合条件，可以包含多个基本条件或其他逻辑组合条件
 * @example ['and', ['=', 'id', 1], ['>', 'age', 18]] - id等于1且age大于18
 * @example ['or', ['=', 'status', 1], ['=', 'status', 2]] - status等于1或2
 */
export type LogicalCondition<T> = [LogicalOperator, ...(BasicCondition<T> | LogicalCondition<T>)[]];

/**
 * 完整的条件类型
 * @description 包含所有可能的条件类型
 */
export type Condition<T> = BasicCondition<T> | LogicalCondition<T> | Record<string, any>;

/**
 * 混合操作选项类型
 * @description 定义混合操作的选项类型
 */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export type CrudRequestOpts<T> = {
  /** 操作类型："create"、"update"、"delete"、"findby" */
  crud?: 'create' | 'update' | 'delete' | 'findby';

  /** 是否使用replace into语法（仅MySQL支持） */
  replace?: boolean;

  /** 是否将驼峰key转成下划线 */
  snake?: boolean;

  /** 指定JSON字段名称，自动转换JSON和字符串 */
  jsoncolumns?: string[];

  /** 查询结果字段风格 */
  rsstyle?: 'camel' | 'snake' | 'kebab';

  /** 主键名称 */
  pk?: string;

  /** 是否由后端生成主键 */
  genkey?: boolean;

  /** 是否使用事务模式 */
  tran?: boolean;

  /** 获取关联表数据 */
  rship?: {
    [relationName: string]: {
      /** 关联表名 */
      tn: string;

      /** 关联表字段映射 */
      cols: Record<string, string>;

      /** 关联条件 */
      where: Condition<any>;

      /** 关联表选项 */
      opts?: {
        /** 数据返回格式："object"(一对一)、"array"(一对多) */
        jsonfn?: 'object' | 'array' | 'object|array';

        /** 嵌套关联查询 */
        rship?: Record<string, any>;

        /** 其他选项 */
        [key: string]: any;
      };
    };
  };
};
/**
 * CRUD请求参数接口
 * @description 定义混合操作的请求参数类型
 */
export type CrudRequestParams<T> = Partial<T> & {
  /** 查询条件 */
  where?: Condition<T>;

  /** 多行数据，用于批量操作 */
  rows?: Array<Partial<T> & Record<string, any>>;

  /** 扩展参数选项 */
  opts?: CrudRequestOpts<T>;
};

/**
 * 表数据服务类
 * @class TableService
 * @template T 数据类型
 */
export class TableService<T> {
  /**
   * 构造函数
   * @param {string} tableName 表名
   */
  constructor(private tableName: string) {}

  /**
   * 混合操作（同时支持增删改）
   * 支持以下功能:
   *   1. 基础的CRUD操作: 通过opts.crud指定"create"、"update"、"delete"、"findby"
   *   2. 批量操作: 通过rows数组提交多条记录
   *   3. 关联查询: 通过opts.rship配置关联表信息
   *   4. 字段处理: 通过opts.jsoncolumns指定JSON字段自动转换
   * @param {CrudRequestParams<T>} options.RequestParams 操作选项（内部可联查表数据）
   * @param {Record<string, any>} options.tables 联查表数据操作 备注: tables RequestParams 建议使用一种方式
   * @param {CrudRequestOpts<T>} options.opts 扩展选项
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 操作结果
   */
  async crud(options: {
    RequestParams?: CrudRequestParams<T>;
    params?: IDefaultParams;
    opts?: CrudRequestOpts<T>;
    tables?: Record<string, CrudRequestParams<T>>;
  }): Promise<any> {
    const { params = {}, RequestParams = {}, opts = {}, tables = {} } = options;
    return await autoPost(
      '/crud',
      {
        [this.tableName]: RequestParams,
        ...tables,
        // 最顶层的配置项 ，不确定会不会穿透到嵌套项的配置 rsstyle 会不会生效安全起见前台都加上
        opts: { tran: true, rsstyle: 'camel', ...opts },
      },
      {
        params: {
          // 联查多表操作标识 , 拼接表名
          tableName:
            Object.keys(tables).join(',').length > 0
              ? Object.keys(tables).join(',')
              : this.tableName,
          ...params,
        },
      },
    );
  }

  /**
   * 分页查询
   * @param {number} options.page 页码
   * @param {number} options.pageSize 每页记录数
   * @param {Condition<T>} options.condition 查询条件
   *   支持以下格式:
   *   1. 对象格式: `{field1: value1, field2: value2}` - 等价于多个AND连接的等于条件
   *   2. 前缀表达式数组:
   *      - 简单条件: `['=', 'fieldName', value]` - 字段等于某值
   *      - 模糊查询: `['like', 'name', '%张%']` - name字段包含"张"
   *      - 范围查询: `['>', 'age', 18]` - age大于18
   *      - IN查询: `['in', 'status', [1,2,3]]` - status在1,2,3中的记录
   *      - AND条件: `['and', ['=','type',1], ['>', 'amount', 100]]` - 类型为1且金额大于100
   *      - OR条件: `['or', ['=','status',1], ['=','status',2]]` - status为1或2的记录
   *      - 复杂嵌套: `['and', ['=','type',1], ['or', ['=','status',1], ['=','status',2]]]`
   * @param {Array<keyof T>} options.columns 要查询的列名数组 默认全部返回
   * @param {CrudRequestOpts<T>} options.opts 扩展选项
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<IPageResult<T>>} 查询结果
   */
  async query(options: {
    page?: number;
    pageSize?: number;
    condition?: Condition<T>;
    columns?: Array<keyof T>;
    params?: IDefaultParams;
    opts?: CrudRequestOpts<T>;
  }): Promise<IPageResult<T>> {
    const { page, pageSize, condition, columns, params, opts = {} } = options;
    const requestParams: any = {
      opts: {
        rsstyle: 'camel',
        ...opts,
      },
    };
    if (page !== undefined) {
      requestParams.page = page;
    }
    if (pageSize) {
      requestParams.pagesize = pageSize;
    }

    if (
      (condition && condition.length > 0) ||
      (condition && typeof condition === 'object' && Object.keys(condition).length > 0)
    ) {
      requestParams.where = condition;
    }

    if (columns && columns.length > 0) {
      requestParams.cols = columns.join(',');
    }

    const result: any = await autoPost(
      '/query',
      {
        [this.tableName]: requestParams,
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
      },
    );

    return {
      total: result.total || 0,
      rows: result.rows || [],
      page,
      pagesize: pageSize,
    };
  }

  /**
   * 根据ID查询单条记录
   * @param {string|number} options.id ID值
   * @param {keyof T} options.idField ID字段名，默认为id
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<T|null>} 查询结果
   */
  async findById(options: {
    id: string | number;
    idField?: keyof T;
    params?: IDefaultParams;
  }): Promise<T | null> {
    const { id, idField = 'id' as keyof T, params } = options;
    const result = await autoPost(
      '/findby',
      {
        [this.tableName]: {
          [idField]: id,
          opts: {
            rsstyle: 'camel', // 驼峰
          },
        },
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
      },
    );

    return result?.[0] ?? null;
  }

  /**
   * 创建记录
   * @param {Partial<T>} options.data 数据对象
   * @param {boolean} options.genKey 是否自动生成主键 默认true
   * @param {keyof T} options.pkField 主键字段名 默认id
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 创建结果
   */
  async create(options: {
    data: Partial<T & { opts: CrudRequestOpts<T> }>;
    genKey?: boolean;
    pkField?: keyof T;
    params?: IDefaultParams;
  }): Promise<any> {
    const { data, genKey = true, pkField = 'id' as keyof T, params } = options;
    const requestParams: any = {
      ...data,
    };
    requestParams.opts = {
      rsstyle: 'camel', // 驼峰
      ...(requestParams.opts ?? {}),
    };
    if (genKey) {
      Object.assign(requestParams.opts, {
        pk: pkField,
        genkey: true,
      });
    }
    return await autoPost(
      '/crud/create',
      {
        [this.tableName]: requestParams,
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
      },
    );
  }

  /**
   * 批量创建记录 暂时弃用批量，文档不准确
   * @param {CrudRequestParams<T>[]} options.list 数据对象数组
   * @param {boolean} options.genKey 是否自动生成主键 默认true
   * @param {keyof T} options.pkField 主键字段名 默认id
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 创建结果
   */
  async batchCreate(options: {
    list: CrudRequestParams<T>[];
    genKey?: boolean;
    pkField?: keyof T;
    params?: IDefaultParams;
  }): Promise<any> {
    const { list = [], genKey = true, pkField = 'oid' as keyof T, params } = options;
    const opts = {
      rsstyle: 'camel', // 驼峰
      crud: 'create',
    };
    if (genKey) {
      Object.assign(opts, {
        pk: pkField,
        genkey: true,
      });
    }
    // 。。。文档骗人的 无语
    // 接口: POST /autoapi/crud 支持混合操作数据
    // 同时update,insert,delete 单行多行数据传参:
    // {
    //     "code": "error",
    //     "message": "Unknow error.",
    //     "request_id": "c951426a-f9e3-4d9c-b42d-2de5b4b73184",
    //     "debug": {
    //         "message": "错误: 对于可变字符类型来说，值太长了(8)",
    //         "data": null
    //     }
    // }
    return await this.crud({
      RequestParams: {
        rows: list,
        opts,
      } as any,
      params,
    });
  }

  /**
   * 更新记录
   * @param {Partial<T>} options.data 要更新的数据
   * @param {Condition<T>} options.condition 更新条件
   *   支持以下格式:
   *   1. 对象格式: `{field1: value1, field2: value2}` - 等价于多个AND连接的等于条件
   *   2. 前缀表达式数组:
   *      - 简单条件: `['=', 'fieldName', value]` - 字段等于某值
   *      - 模糊查询: `['like', 'name', '%张%']` - name字段包含"张"
   *      - 范围查询: `['>', 'age', 18]` - age大于18
   *      - AND条件: `['and', ['=','type',1], ['>', 'amount', 100]]` - 类型为1且金额大于100
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 更新结果
   */
  async update(options: {
    data: Partial<T>;
    condition: Condition<T>;
    params?: IDefaultParams;
  }): Promise<any> {
    const { data, condition, params } = options;
    return await autoPost(
      '/crud/update',
      {
        [this.tableName]: {
          ...data,
          where: condition,
          opts: {
            rsstyle: 'camel', // 驼峰
          },
        },
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
      },
    );
  }

  /**
   * 批量更新记录 暂时弃用批量，文档不准确
   * @param {CrudRequestParams<T>[] | any[]} options.list 数据对象或自定义{where: Condition<T>， ...T}数组
   * @param {keyof T} options.pkField 主键字段名 默认id
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 创建结果
   */
  batchUpdate(options: {
    list: Condition<T>[] | any[];
    pkField?: keyof T;
    params?: IDefaultParams;
  }): Promise<any> {
    const { list, pkField = 'id' as keyof T, params } = options;
    const rows = list.map((con) => {
      if (con?.where) {
        return con;
      } else {
        if (!con[pkField]) {
          throw new Error(`主键字段${pkField as string}值不能为空`);
        }
        return {
          where: {
            [pkField]: con[pkField],
          },
          ...con,
        };
      }
    });
    return this.crud({
      RequestParams: {
        rows,
        opts: {
          rsstyle: 'camel', // 驼峰
          crud: 'update',
        },
      } as any,
      params,
    });
  }

  /**
   * 单个删除记录
   * @param {<T>} options.data 删除条件
   *   支持以下格式:
   *   1. 对象格式: `{id: 1001}` - 删除id等于1001的记录
   *   2. 前缀表达式数组: 参考查询条件说明
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 删除结果
   */
  async delete(options: { data: Partial<T>; params?: IDefaultParams }): Promise<any> {
    const { data, params } = options;
    return await autoPost(
      '/crud/delete',
      {
        [this.tableName]: {
          ...data,
          opts: {
            del: true,
            rsstyle: 'camel', // 驼峰
          },
        },
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
      },
    );
  }

  /**
   * 批量删除记录 暂时弃用批量，文档不准确
   * @param {(CrudRequestParams<T> | string | number)[]} options.list 数据对象或自定义{where: Condition<T>}数组
   * @param {keyof T} options.pkField 主键字段名 默认id
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 创建结果
   */
  batchDelete(options: {
    list: (CrudRequestParams<T> | string | number)[];
    pkField?: keyof T;
    params?: IDefaultParams;
  }): Promise<any> {
    const { list = [], pkField = 'id' as keyof T, params } = options;
    const rows = list.map((con) => {
      if (typeof con === 'object' && con?.where) {
        return con;
      } else {
        if (!con) {
          throw new Error(`主键字段${pkField as string}值不能为空`);
        }
        return {
          where: {
            [pkField]: con,
          },
        };
      }
    });
    return this.crud({
      RequestParams: {
        rows,
        opts: {
          del: true,
          rsstyle: 'camel', // 驼峰
          crud: 'delete',
        },
      } as any,
      params,
    });
  }

  /**
   * 导出数据为CSV 导出暂未定能不能用，返回的内容不是CSV文件 不如使用vxe获取全部内置导出功能
   * @param {T[]} options.data 要导出的数据
   * @param {string} options.header CSV头部字段定义
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 导出结果
   */
  async exportToCsv(options: {
    data: T[];
    header: (keyof T | string)[];
    params?: IDefaultParams;
  }): Promise<any> {
    const { data, header, params } = options;
    return await autoPost(
      '/data_to_csv',
      {
        header: header.join(','),
        rows: data,
        opts: {
          // rsstyle: "camel", // 驼峰
        },
      },
      {
        params: {
          tableName: this.tableName,
          ...params,
        },
        responseType: 'blob',
      },
    );
  }

  /**
   * 导入csv文件到数据库 接口名称: POST /import_csv
   * body类型: form-data
   * 参数说明:
   * table 定义表名称
   * header 定义字段映射 例如: "fiedl1,字段1,field2,字段2"
   * file 上传的csv文件名称
   * opts 扩展选项 (可选)
   *    {"pk":{"gen":true,"name":"id","type":"uuid"},"preview":true,"encoding":"GB2123"}
   *     pk       指定导入数据的主键生成策略
   *     gen    是否追加主键列
   *     name   主键列的名称
   *     type   生成主键的策略  可选值 "uuid" 默认生成uuid
   *     priview  返回导入的数据, 不实际导入数据
   *     encoding 打开文件使用的编码 "GB2312" "GBK" "UTF8"
   * @param {File} options.file 要导入的csv文件
   * @param {string} options.header CSV头部字段定义
   * @param {string} options.encoding 打开文件使用的编码 "GB2312" "GBK" "UTF8"
   * @param {boolean} options.preview 是否返回导入的数据, 不实际导入数据
   * @param {keyof T} options.pkField 主键字段名 默认 oid 如果空值则自定义 生成主键的策略 "uuid"
   * @param {IDefaultParams} options.params 请求参数
   * @returns {Promise<any>} 导入结果
   */
  async importToCsv(options: {
    file: File;
    header: (keyof T | string)[];
    preview?: boolean;
    encoding?: 'GB2312' | 'GBK' | 'UTF8';
    pkField?: keyof T;
    params?: IDefaultParams;
  }): Promise<any> {
    const {
      file,
      header,
      preview = true,
      encoding = 'UTF8',
      pkField = 'oid' as keyof T,
      params,
    } = options;

    // 创建FormData对象
    const formData = new FormData();
    formData.append('file', file);
    formData.append('table', this.tableName);
    formData.append('header', header.join(','));
    // 添加选项
    const opts = {
      pk: {
        gen: !!pkField,
        name: pkField,
        type: 'uuid',
      },
      preview,
      encoding,
    };
    // 将选项转换为字符串并添加到FormData
    formData.append('opts', JSON.stringify(opts));

    // 为后端API准备参数
    const requestParams: Record<string, any> = {
      tableName: this.tableName,
      ...params,
    };

    // 将params参数添加到URL查询参数中
    const queryParams = new URLSearchParams();
    for (const key in requestParams) {
      if (Object.prototype.hasOwnProperty.call(requestParams, key)) {
        queryParams.append(key, String(requestParams[key]));
      }
    }

    return await autoPost(
      `/import_csv?${queryParams.toString()}`,
      {
        table: this.tableName,
        header: header.join(','),
        file,
        opts,
      },
      {
        // data: formData,
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      },
    );
  }
}

/**
 * 创建表服务实例
 * @param {string} tableName 表名
 * @returns {TableService<T>} 表服务实例
 */
export function createTableService<T>(tableName: string): TableService<T> {
  return new TableService<T>(tableName);
}

export default TableService;

// 示例代码已移至 AutoTableServiceExamples.ts 文件
// 参考该文件获取详细的使用示例
